package model;

import error.GedivaException;
import filter.*;
import graphs.AbstractGraph;

import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.*;

import parsers.IParser;

public class Model implements IGedivaModel
{
    private static final double TEXT_CUTOFF_PERCENTAGE = .8;
    private static final String PRIMITIVE_PREFIX = "java.lang.";
    private ResourceBundle mySupportedGraphs = ResourceBundle
            .getBundle("properties/SupportedGraphTypes");
    private ResourceBundle mySupportedFilters = ResourceBundle
            .getBundle("properties/SupportedFilterTypes");
    private ResourceBundle myGraphDataTypes = ResourceBundle
            .getBundle("properties/GraphDataTypes");

    @Override
    public Map<Object, List<Object>> applyFilters(
            Map<Object, List<Object>> data, List<IFilter<?, ?>> filters)
    {
        Map<Object, List<Object>> result = new TreeMap<Object, List<Object>>(
                data);
        for (IFilter<?, ?> filter : filters)
            result = filter.filterData(result);
        return result;
    }

    @Override
    public SortedMap<Object, List<Object>> getData(URL path,
            AbstractGraph<?, ?> displayType)
    {
        List<List<String>> data = parseFile(path);
        List<Class<?>> dataTypes = readInTypes(displayType);
        SortedMap<Object, List<Object>> result = new TreeMap<Object, List<Object>>();
        if (data.isEmpty())
            return result;
        if (isText(data))
            return generateTagData(data);
        if (isHeader(data.get(0), dataTypes))
            data.remove(0);
        for (List<String> line : data)
        {
            Object x = resolveStringToData(line.get(0), dataTypes.get(0));
            List<Object> y = new ArrayList<Object>();
            for (int i = 1; i < line.size(); i++)
                y.add(resolveStringToData(line.get(i), dataTypes.get(1)));
            while (result.containsKey(x))
            {
                try
                {
                    x = ((Double) x + 0.0001);
                }
                catch (Exception e)
                {
                    x = x + " ";
                }
            }
            result.put(x, new ArrayList<Object>());
            result.get(x).addAll(y);
        }
        return result;
    }

    @Override
    public List<AbstractGraph<?, ?>> getDisplayTypes(URL url)
    {
        String path = url.getFile();
        int periodIndex = path.lastIndexOf(".");
        if (periodIndex == -1)
            throw new GedivaException("Invalid file type");
        String thisExtension = path.substring(periodIndex);
        String[] supportedItems;
        ArrayList<AbstractGraph<?, ?>> result = new ArrayList<AbstractGraph<?, ?>>();
        try
        {
            supportedItems = mySupportedGraphs.getString(thisExtension).split(
                    "\\s*,\\s*");
        }
        catch (MissingResourceException e)
        {
            throw new GedivaException("No supported graphs.");
        }
        for (String s : supportedItems)
        {
            try
            {
                Constructor<?> con = Class.forName(s).getConstructor();
                Object item = con.newInstance();
                if (item instanceof AbstractGraph<?, ?>)
                    result.add((AbstractGraph<?, ?>) item);
            }
            catch (Exception e)
            {
                throw new GedivaException("");
            }
        }
        return result;
    }

    @Override
    public List<IFilter<?, ?>> getFilters(AbstractGraph<?, ?> displayType)
    {
        String displayString = displayType.toString();
        String[] supportedItems;
        ArrayList<IFilter<?, ?>> result = new ArrayList<IFilter<?, ?>>();
        try
        {
            supportedItems = mySupportedFilters.getString(displayString).split(
                    "\\s*,\\s*");
        }
        catch (MissingResourceException e)
        {
            return result;
        }
        for (String s : supportedItems)
        {
            try
            {
                Constructor<?> con = Class.forName(s).getConstructor(
                        new Class[0]);
                Object item = con.newInstance(new Object[0]);
                if (item instanceof IFilter<?, ?>)
                    result.add((IFilter<?, ?>) item);
            }
            catch (Exception e)
            {
            }
        }
        return result;
    }

    @Override
    public List<String> getHeaders(URL url, AbstractGraph<?, ?> displayType)
    {
        String path = url.getFile().substring(
                url.getFile().lastIndexOf("/") + 1);
        if (path.toUpperCase().endsWith(".TXT"))
            return Arrays.asList(path, "word", "frequency");
        List<Class<?>> dataTypes = readInTypes(displayType);
        try
        {
            List<String> result = new ArrayList<String>();
            result.add(path);
            String name = (path.substring(path.lastIndexOf(".")).toUpperCase() + "Parser");
            Constructor<?> construct = Class.forName("parsers" + name)
                    .getConstructor();
            Object p = construct.newInstance();
            if (p instanceof IParser)
            {
                List<String> headers = ((IParser) p).parseFirstLine(url);
                if (isHeader(headers, dataTypes))
                {
                    result.addAll(headers);
                }
                else
                {
                    for (int k = 0; k < ((IParser) p).parseFirstLine(url)
                            .size(); k++)
                        result.add("");
                }
                return result;
            }
            throw new GedivaException("Unsupported file type");
        }
        catch (GedivaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new GedivaException("Unsupported file type");
        }
    }

    @Override
    public void sortOnColumn(List<List<Object>> data, int columnNumber,
            boolean ascending)
    {
        // TODO Auto-generated method stub
        throw new GedivaException("NOT IMPLEMENTED");
    }

    private SortedMap<Object, List<Object>> generateTagData(
            List<List<String>> data)
    {
        SortedMap<Object, List<Object>> result = new TreeMap<Object, List<Object>>();
        for (List<String> line : data)
            for (Object s : line)
                if (result.get(s) == null)
                    result.put(s, Arrays.asList((Object) 1.0));
                else
                    result.put(s, Arrays.asList((Object) ((Double) result
                            .get(s).get(0) + 1)));
        return result;
    }

    private boolean isHeader(List<String> fields, List<Class<?>> dataTypes)
    {
        for (int i = 0; i < fields.size(); i++)
            try
            {
                resolveStringToData(fields.get(i), dataTypes.get(i));
            }
            catch (Exception e)
            {
                return true;
            }
        return false;
    }

    private boolean isText(List<List<String>> input)
    {
        double size = 0;
        double notNums = 0;
        for (List<String> line : input)
            for (String s : line)
                try
                {
                    size++;
                    resolveStringToData(s, Double.class);
                }
                catch (Exception e)
                {
                    notNums++;
                }
        if (notNums / size > TEXT_CUTOFF_PERCENTAGE)
            return true;
        return false;
    }

    private List<List<String>> parseFile(URL path)
    {
        String file = path.getFile();
        try
        {
            String name = (file.substring(file.lastIndexOf(".")).toUpperCase() + "Parser");
            Constructor<?> construct = Class.forName("parsers" + name)
                    .getConstructor();
            Object p = construct.newInstance();
            if (p instanceof IParser)
                return ((IParser) p).parseURL(path);
            throw new GedivaException("Unsupported file type");
        }
        catch (Exception e)
        {
            throw new GedivaException("Unsupported file type");
        }
    }

    private List<Class<?>> readInTypes(AbstractGraph<?, ?> displayType)
    {
        String displayString = displayType.toString();
        String[] supportedItems;
        ArrayList<Class<?>> result = new ArrayList<Class<?>>();
        try
        {
            supportedItems = myGraphDataTypes.getString(displayString).split(
                    "\\s*,\\s*");
        }
        catch (MissingResourceException e)
        {
            return result;
        }
        for (String s : supportedItems)
        {
            try
            {
                Class<?> item = Class.forName(PRIMITIVE_PREFIX + s);
                result.add(item);
            }
            catch (Exception e)
            {
            }
        }
        return result;
    }

    private Object resolveStringToData(String s, Class<?> t)
    {
        try
        {
            return t.getConstructor(new Class[] { String.class })
                    .newInstance(s);
        }
        catch (Exception e)
        {
            throw new GedivaException("Improper data inputted");
        }
    }

}
